/*
 * sm2-1.0.js
 * 
/**
 * @name sm2-1.0.js
 * @version 1.0.0
 */

var debug = false;
//国密推荐曲线
var sm2_ecParams = {
    "p": "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
    "a": "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
    "b": "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
    "n": "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
    "gx": "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
    "gy": "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0",
    "keylen": 256
};

function SM2() {

    this.ecc_p = new BigInteger(sm2_ecParams['p'], 16);
    this.ecc_a = new BigInteger(sm2_ecParams['a'], 16);
    this.ecc_b = new BigInteger(sm2_ecParams['b'], 16);
    this.ecc_n = new BigInteger(sm2_ecParams['n'], 16);
    this.ecc_gx = new BigInteger(sm2_ecParams['gx'], 16);
    this.ecc_gy = new BigInteger(sm2_ecParams['gy'], 16);
    this.rng = new SecureRandom();

    this.ecCurve = new ECCurveFp(this.ecc_p, this.ecc_a, this.ecc_b);
    this.ecPointG = ECPointFp.decodeFromHex(this.ecCurve, "04" + sm2_ecParams['gx'] + sm2_ecParams['gy']);
    if (debug == true) {
        console.log("ax1=" + this.ecCurve.getA().toBigInteger().toString(16));
        console.log("by1=" + this.ecCurve.getB().toBigInteger().toString(16));
        console.log("gx1=" + this.ecPointG.getX().toBigInteger().toString(16));
        console.log("gy1=" + this.ecPointG.getY().toBigInteger().toString(16));
    }
}

SM2.prototype = {
    getBigRandom: function (limit) {
        return new BigInteger(limit.bitLength(), this.rng)
            .mod(limit.subtract(BigInteger.ONE))
            .add(BigInteger.ONE)
            ;
    },
    generateKeyPairHex: function () {
        var key = this.generateKeyPairBigInteger();
        var biX = key['pubkey'].getX().toBigInteger();
        var biY = key['pubkey'].getY().toBigInteger();

        var charlen = sm2_ecParams['keylen'] / 4;
        var hPrv = ("0000000000" + key['privkey'].toString(16)).slice(-charlen);
        var hX = ("0000000000" + biX.toString(16)).slice(-charlen);
        var hY = ("0000000000" + biY.toString(16)).slice(-charlen);
        var hPub = "04" + hX + hY;

        return {'privkeyhex': hPrv, 'pubkeyhex': hPub};
    },
    generateKeyPairBigInteger: function () {
        var biN = this.ecc_n;
        var biPrv = null;
        var epPub = null;
        while (true) {
            do {
                biPrv = this.getBigRandom(biN);
            }
            while (biPrv.equals(BigInteger.ZERO) || biPrv.compareTo(biN) >= 0 || biPrv.bitLength() < 249);

            epPub = this.ecPointG.multiply(biPrv);
            if (epPub.getX().toBigInteger().bitLength() >= 249 && epPub.getY().toBigInteger().bitLength() >= 249) {

                break;
            }
        }
        return {'privkey': biPrv, "pubkey": epPub};
    },
    formartXY: function (bg, needLength) {

        var tmp = new Array(needLength);
        for (var i = 0; i < tmp.length; i++) {
            tmp[i] = 0;
        }
        var bgByte = bg.toByteArray();
        if (bgByte == null) {
            return null;
        }

        if (bgByte.length > needLength) {
            arrayCopy(bgByte, bgByte.length - needLength, tmp, 0, needLength);
        } else if (bgByte.length == needLength) {
            tmp = bgByte;
        } else {
            arrayCopy(bgByte, 0, tmp, needLength - bgByte.length, bgByte.length);
        }

        return tmp;
    },
    kdf: function (xy, data) {

//		int ct = 1;
        // var loop = data.length%32!=0 ? (data.length/32+1):data.length/32;
        var loop = Math.ceil(data.length / 32);//向上取整
        var sm3;
        var hash = new Array(32);

        for (var i = 0; i < loop; i++) {
            sm3 = new SM3Digest();
            sm3.update(xy, 0, xy.length);
            sm3.update(intToByte(i + 1), 0, 4);
            hash = sm3.doFinal();

            for (var j = 0; j < hash.length && (i * 32 + j) < data.length; j++) {
                data[i * 32 + j] ^= hash[j];
            }
        }

        return 0;
    },
    arrayCompare: function (src1, pos1, src2, pos2, len) {

        if (src1.length - pos1 < len) {
            return -1;
        }
        if (src2.length - pos2 < len) {
            return -1;
        }
        for (var i = 0; i < len; i++) {
            if (src1[pos1++] != src2[pos2++]) {
                return -1;
            }
        }

        return 0;
    },
    encrypt: function (pubkey, dataHex) {
        var cipher;

        //1、创建sm2曲线，公钥P1加压缩标志0x04	2、将公钥转为曲线上的点 u1
        //3、生成随机公私钥:k、P2 ，新公钥P2即为（x1,y1）点 ，x1y1拼接，即为:c1 （注意长度保持64字节）
        //4、新私钥k与公钥P1相乘，即为（x2,y2）点，通过kdf算法，得到t字符串，t字符串和明文做异或运算，得到c2
        //5、x2||明文||y2 三者拼接然后做sm3运算，得到c3
        //说明，kdf主要是：循环次数loop（明文字节长度除以32,向上取整，运算时依次从1到最大值）拼接x2y2，即：x2||y2||loop（loop是int类型，须转成四字节），
        //然后对拼接后的字符串做sm3运算，依次将每次的结果拼接，得到最终结果
        if (pubkey == null || pubkey.length == 0 || dataHex == null || dataHex.length == 0) {
            return null;
        }

        if (pubkey.length == 128) {
            pubkey = "04" + pubkey;
        }

        var data = Hex.decode(dataHex);
        var userKey = ECPointFp.decodeFromHex(this.ecCurve, pubkey);
        var c2 = null;
        var c1 = null;
        var x2 = null;
        var y2 = null;
        var loop = 0;
        do {
            var kp = this.generateKeyPairBigInteger();//创建公私钥对，这里私钥当做随机数k，公钥为c1点(x1,y1)
            if (debug == true) {
                console.log("priv" + kp['privkey'].toString(16));
                console.log("x1=" + kp['pubkey'].getX().toBigInteger().toString(16));
                console.log("y1=" + kp['pubkey'].getY().toBigInteger().toString(16));
            }
            c1 = kp['pubkey'];


            var x2y2 = userKey.multiply(kp['privkey']);//(x2,y2)
            x2 = this.formartXY(x2y2.getX().toBigInteger(), 32);
            y2 = this.formartXY(x2y2.getY().toBigInteger(), 32);
            if (debug == true) {
                console.log("x2=" + x2);
                console.log("y2=" + y2);
                console.log("x2=" + Hex.encode(x2, 0, x2.length));
                console.log("y2=" + Hex.encode(y2, 0, y2.length));
            }

            c2 = new Array(data.length);
            arrayCopy(data, 0, c2, 0, data.length);
            var xy = new Array(x2.length + y2.length);
            arrayCopy(x2, 0, xy, 0, x2.length);
            arrayCopy(y2, 0, xy, x2.length, y2.length);
            this.kdf(xy, c2);

            loop++;

        } while (this.arrayCompare(c2, 0, data, 0, data.length) == 0 && loop < 10);
        if (loop >= 10) {//失败超过一定次数，不再尝试
            return null;
        }

        var sm3 = new SM3Digest();
        sm3.update(x2, 0, x2.length);
        sm3.update(data, 0, data.length);
        sm3.update(y2, 0, y2.length);
        var c3 = sm3.doFinal();
        if (debug == true) {
            console.log("data=" + Hex.encode(data, 0, data.length));
            console.log("c3=" + Hex.encode(c3, 0, c3.length));
        }


//		cipher = new Array(96+c2.length);

        /*var c1x = this.formartXY(c1.getX().toBigInteger(), 33);
        var c1y = this.formartXY(c1.getY().toBigInteger(), 33);*/
        var c1x = c1.getX().toBigInteger().toByteArray();
        var c1y = c1.getY().toBigInteger().toByteArray();
        /*arrayCopy(c1x, 0, cipher, 0, c1x.length);
        arrayCopy(c1y, 0, cipher, c1x.length, c1y.length);
        arrayCopy(c3, 0, cipher, c1x.length+c1y.length, c3.length);
        arrayCopy(c2, 0, cipher, c1x.length+c1y.length+c3.length, c2.length);
        console.log(Hex.encode(cipher,0,cipher.length));*/
        var cipher = this.cipherToDer(c1x, c1y, c2, c3);

        return Hex.encode(cipher, 0, cipher.length);
    },
    decrypt: function (privkey, cipherHex) {
        cipher = Hex.decode(cipherHex);
        var dec = this.derDecode(cipher);

        var c1 = new Array(64 + 1);
        var c2 = new Array(dec["c2"].length);
        var c3 = new Array(32);

        //1、创建sm2曲线，截取密文前64字节，转化为点（x1,y1）
        //2、点（x1,y1）与私钥k相乘，得到（x2,y2）
        //3、x2y2通过kdf算法得到字符串t，t再和密文c2做异或运算，得到明文M
        //4、x2||M||y2 拼接，然后做sm3运算，得到hash值，对比密文的c3，一致则解密成功
        for (var i = 0; i < c1.length; i++) {
            c1[i] = 0;
        }
        if (dec["c1x"].length <= 32) {//由于c1x/y两个元素长度可能不等于32，须调整为32
            arrayCopy(dec["c1x"], 0, c1, 1 + (32 - dec["c1x"].length), dec["c1x"].length);
        } else {
            arrayCopy(dec["c1x"], dec["c1x"].length - 32, c1, 1, 32);
        }
        if (dec["c1y"].length <= 32) {
            arrayCopy(dec["c1y"], 0, c1, 1 + 32 + (32 - dec["c1y"].length), dec["c1y"].length);
        } else {
            arrayCopy(dec["c1y"], dec["c1y"].length - 32, c1, 1 + 32, 32);
        }

        c1[0] = 0x04;
        var c1Point = ECPointFp.decodeFromHex(this.ecCurve, Hex.encode(c1, 0, c1.length));

        var x2y2Point = c1Point.multiply(new BigInteger(privkey, 16));
        var x2 = this.formartXY(x2y2Point.getX().toBigInteger(), 32);
        var y2 = this.formartXY(x2y2Point.getY().toBigInteger(), 32);
        var xy = new Array(x2.length + y2.length);
        arrayCopy(x2, 0, xy, 0, x2.length);
        arrayCopy(y2, 0, xy, x2.length, y2.length);

        arrayCopy(dec["c2"], 0, c2, 0, c2.length);
        var c2Copy = new Array(c2.length);
        arrayCopy(c2, 0, c2Copy, 0, c2.length);
        this.kdf(xy, c2);
        if (this.arrayCompare(c2Copy, 0, c2, 0, c2.length) == 0) {
            console.log("t is all 0 and decrypt is failed!");
            return null;
        }

        var sm3 = new SM3Digest();
        var hash = new Array(32);
//		arrayCopy(cipher, c1.length-1, c3, 0, c3.length);
        sm3.update(x2, 0, x2.length);
        sm3.update(c2, 0, c2.length);
        sm3.update(y2, 0, y2.length);
        hash = sm3.doFinal();

        if (this.arrayCompare(hash, 0, dec["c3"], 0, 32) != 0) {
            return null;
        }


        return Hex.encode(c2, 0, c2.length);
    },
    cipherToDer: function (c1x, c1y, c2, c3) {
        var c2Len = c2.length;
        var c2Tag = [];
        if (c2Len < 0x80) {
            c2Tag[0] = 0x04;
            c2Tag[1] = c2Len;
        } else {
            c2Tag[0] = 0x04;
            var c2LenBytes = intToByte(c2Len);
            var i = 0;
            while (c2LenBytes[i] == 0 && i < c2LenBytes.length) {
                i++;
            }
            c2Tag[1] = 0x80 | (c2LenBytes.length - i);
            for (var j = 2; i < c2LenBytes.length; i++, j++) {
                c2Tag[j] = c2LenBytes[i];
            }
        }
        var totalTagLen = c1x.length + c1y.length + c2.length + c3.length + 6 + c2Tag.length;
        var totalTag = [];
        totalTag[0] = 0x30;
        if (totalTagLen < 0x80) {
            totalTag[1] = totalTagLen;
        } else {
            var totalTagLenBytes = intToByte(totalTagLen);
            var i = 0;
            while (totalTagLenBytes[i] == 0 && i < totalTagLenBytes.length) {
                i++;
            }
            totalTag[1] = 0x80 | (totalTagLenBytes.length - i);
            for (var j = 2; i < totalTagLenBytes.length; i++, j++) {
                totalTag[j] = totalTagLenBytes[i];
            }
        }

        var der = new Array(totalTagLen + totalTag.length);
        var derLen = 0;

        arrayCopy(totalTag, 0, der, 0, totalTag.length);
        derLen += totalTag.length;

        der[derLen++] = 0x02;
        der[derLen++] = c1x.length;
        arrayCopy(c1x, 0, der, derLen, c1x.length);
        derLen += c1x.length;

        der[derLen++] = 0x02;
        der[derLen++] = c1y.length;
        arrayCopy(c1y, 0, der, derLen, c1y.length);
        derLen += c1y.length;

        der[derLen++] = 0x04;
        der[derLen++] = c3.length;
        arrayCopy(c3, 0, der, derLen, c3.length);
        derLen += c3.length;

        arrayCopy(c2Tag, 0, der, derLen, c2Tag.length);
        derLen += c2Tag.length;
        arrayCopy(c2, 0, der, derLen, c2.length);

        return der;
    },
    derDecode: function (der) {
        var pos = 0;
        var totalLen = 0;

        if (der[pos++] != 0x30) {
            return null;
        }
        if ((der[pos] & 0xFF) <= 0x7F) {
//			totalLen = der[pos++] & 0xFF;
            pos++;
        } else {
            pos += (der[pos] & 0x7F) + 1;
        }

        pos++;
        var c1xLen = der[pos];
        var c1x = new Array(c1xLen);
        arrayCopy(der, ++pos, c1x, 0, c1xLen);
        pos += c1xLen;

        pos++;
        var c1yLen = der[pos];
        var c1y = new Array(c1yLen);
        arrayCopy(der, ++pos, c1y, 0, c1yLen);
        pos += c1yLen;

        pos++;
        var c3Len = der[pos];
        var c3 = new Array(c3Len);
        arrayCopy(der, ++pos, c3, 0, c3Len);
        pos += c3Len;

        pos++;
        var c2Len = 0;
        if ((der[pos] & 0xFF) <= 0x7F) {
            c2Len = der[pos] & 0xFF;
        } else {
            for (var i = 0, j = (der[pos] & 0x7F) - 1; i < (der[pos] & 0x7F); i++, j--) {
                c2Len = c2Len | ((der[pos + i + 1] & 0xFF) << (j * 8));
            }
            pos += (der[pos] & 0x7F);
        }
        var c2 = new Array(c2Len);
        arrayCopy(der, ++pos, c2, 0, c2Len);
        pos += c2Len;

        return {'c1x': c1x, "c1y": c1y, "c2": c2, "c3": c3};
    },
    sm2encrypt: function (inputtext) {
        var pubkey = "04A03AA1FC6D51F06F1717916A3D541CE5B441CD7A9378E76A3CE6D386A8D357D9BAE8EE008BC2C10CC57E929FE273280808737DFE7A5636D118821E272D655D8B";
        if (inputtext.length <= 0) {
            alert("输入数据不能为空");
        }
        var sm2DataHex = Hex.utf8StrToHex(inputtext);//转成byte数组
        //sm2加密
        var cipher = this.encrypt(pubkey, sm2DataHex);//函数输入输出都是16进制数据
        return cipher;
    },
    sm2decrypt: function (inputtext) {
        var privkey = "E946BA9A535AFA0F204BEBF47B0C83E555D7C382797C3F1635948E38138A0127";
        if (inputtext.length <= 0) {
            alert("输入数据不能为空");
        }
        //sm2解密
        var plain = this.decrypt(privkey, inputtext);
        var utf8Str = Hex.hexToUtf8Str(plain);
        return utf8Str;
    }

}